package gorm

import (
	"bufio"
	"fmt"
	"gitee.com/symbol365/tool/gorm/config"
	"github.com/spf13/cobra"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
	"os"
	"strings"
)

type FieldInfo struct {
	Field   string
	Type    string
	Null    string
	Key     string
	Default string
	Extra   string
	Comment string
}

var CmdGorm = &cobra.Command{
	Use:     "gorm",
	Short:   "base on gorm tools for mysql database to golang struct",
	Run:     Generate,
	Example: "tool gorm  --table=user",
}

var host string
var port string
var username string
var password string
var database string
var table string

func init() {
	err := config.InitConfig()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	host = config.Conf.Host
	port = config.Conf.Port
	username = config.Conf.User
	password = config.Conf.Password
	database = config.Conf.Dbname
	CmdGorm.Flags().StringVarP(&table, "table", "", table, "表名")

}

func Generate(cmd *cobra.Command, args []string) {
	fmt.Println("模版生成中...")
	if table == "" {
		fmt.Println("table is empty")
		return
	}
	// 链接数据库
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, database)
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		fmt.Println("failed to connect database")
	}

	var fields []FieldInfo

	tableName := table
	query := "show full columns from `" + tableName + "`"
	db.Raw(query).Scan(&fields)

	query = "SELECT INDEX_NAME,COLUMN_NAME,NON_UNIQUE FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ;"
	rows, _ := db.Raw(query, database, tableName).Rows()

	type FileIndex struct {
		NonUnique  int    `gorm:"Non_unique"`
		KeyName    string `json:"Key_name"`
		ColumnName string `json:"Column_name"`
	}

	var fieldIndexes []FileIndex

	for rows.Next() {
		var indexName, columnName string
		var nonUnique int
		if err = rows.Scan(&indexName, &columnName, &nonUnique); err != nil {
			log.Fatal(err)
		}
		fieldIndexes = append(fieldIndexes, FileIndex{
			NonUnique:  nonUnique,
			KeyName:    indexName,
			ColumnName: columnName,
		})
	}

	columnString := ""
	tmp := ""
	jsonTmp := ""
	for _, field := range fields {
		gorm := ""
		if field.Extra == "auto_increment" {
			gorm = gorm + "autoIncrement:true;"
		}

		for _, fieldIndex := range fieldIndexes {
			if fieldIndex.ColumnName == field.Field {
				if fieldIndex.KeyName == "PRIMARY" {
					gorm = gorm + "primaryKey;"
				} else {
					if fieldIndex.NonUnique == 0 {
						gorm = gorm + "uniqueIndex:" + fieldIndex.KeyName + ";"
					} else {
						gorm = gorm + "index:" + fieldIndex.KeyName + ";"
					}
				}
			}
		}

		gorm = gorm + "column:" + field.Field + ";"
		gorm = gorm + "type:" + field.Type + ";"

		if field.Null == "NO" {
			gorm = gorm + "not null;"
		}

		if field.Null == "YES" {
			if field.Field != "created_at" && field.Field != "updated_at" && field.Field != "deleted_at" {
				gorm = gorm + "default:null;"
			}
		}

		gorm = gorm + "comment:" + field.Comment + ";"

		if field.Field == "deleted_at" {
			tmp = fmt.Sprintf("  %s  %s", UnderLineToHump(field.Field), "soft_delete.DeletedAt")
			jsonTmp = fmt.Sprintf("   `gorm:\"%s\" json:\"%s\"` // %s\n", gorm, "-", field.Comment)
			columnString = columnString + tmp + jsonTmp
		} else {
			tmp = fmt.Sprintf("  %s  %s", UnderLineToHump(field.Field), typeConvert(Split(field.Type, "(")[0]))
			if typeConvert(Split(field.Type, "(")[0]) == "int64" {
				jsonTmp = fmt.Sprintf("   `gorm:\"%s\" json:\"%s,string\"`  // %s\n", gorm, field.Field, field.Comment)
			} else {
				jsonTmp = fmt.Sprintf("   `gorm:\"%s\" json:\"%s\"`  // %s\n", gorm, field.Field, field.Comment)
			}

			columnString = columnString + tmp + jsonTmp
		}

	}
	rs := fmt.Sprintf("package %s \n\nimport(\n   \"gorm.io/gorm\"\n)\n\ntype %s struct{\n%s}", RemoveUnderLine(tableName)+"model", UnderLineToHump(HumpToUnderLine(tableName)), columnString)

	// 目录路径
	dirPath := "./internal/model/" + RemoveUnderLine(tableName)

	// 检查目录是否存在
	if _, err = os.Stat(dirPath); os.IsNotExist(err) {
		// 目录不存在，创建目录
		err = os.Mkdir(dirPath, 0755)
		if err != nil {
			fmt.Println("创建目录失败:", err)
			return
		}
	}
	//打开文件或创建文件
	filePath := "./internal/model/" + RemoveUnderLine(tableName) + "/" + table + ".go"
	file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Printf("open file err=%v\n", err)
		return
	}
	//
	defer func(file *os.File) {
		err = file.Close()
		if err != nil {
			fmt.Println("close file err=%v\n", err)
			return
		}
	}(file)

	//写入时使用带缓存的*writer
	writer := bufio.NewWriter(file)
	_, err = writer.WriteString(rs)
	if err != nil {
		fmt.Println("writeString) err=%v\n", err)
		return
	}
	err = writer.Flush()
	if err != nil {
		fmt.Println("flush err=%v\n", err)
		return
	}
	fmt.Println("生成已完成...")
}

// Split 增强型Split，对  a,,,,,,,b,,c     以","进行切割成[a,b,c]
func Split(s string, sub string) []string {
	var rs = make([]string, 0, 20)
	tmp := ""
	Split2(s, sub, &tmp, &rs)
	return rs
}

// Split2 附属于Split，可独立使用
func Split2(s string, sub string, tmp *string, rs *[]string) {
	s = strings.Trim(s, sub)
	if !strings.Contains(s, sub) {
		*tmp = s
		*rs = append(*rs, *tmp)
		return
	}
	for i := range s {
		if string(s[i]) == sub {
			*tmp = s[:i]
			*rs = append(*rs, *tmp)
			s = s[i+1:]
			Split2(s, sub, tmp, rs)
			return
		}
	}
}

// FindUpperElement 找到字符串中大写字母的列表,附属于HumpToUnderLine
func FindUpperElement(s string) []string {
	var rs = make([]string, 0, 10)
	for i := range s {
		if s[i] >= 65 && s[i] <= 90 {
			rs = append(rs, string(s[i]))
		}
	}
	return rs
}

// HumpToUnderLine 驼峰转下划线
func HumpToUnderLine(s string) string {
	if s == "ID" {
		return "id"
	}
	var rs string
	elements := FindUpperElement(s)
	for _, e := range elements {
		s = strings.Replace(s, e, "_"+strings.ToLower(e), -1)
	}
	rs = strings.Trim(s, " ")
	rs = strings.Trim(rs, "\t")
	return strings.Trim(rs, "_")
}

// UnderLineToHump 下划线转驼峰
func UnderLineToHump(s string) string {
	arr := strings.Split(s, "_")
	for i, v := range arr {
		arr[i] = strings.ToUpper(string(v[0])) + string(v[1:])
	}
	return strings.Join(arr, "")
}

// RemoveUnderLine 去除下划线
func RemoveUnderLine(s string) string {
	return strings.Replace(s, "_", "", -1)
}

// 类型转换pg->go
func typeConvert(s string) string {
	if strings.Contains(s, "char") || in(s, []string{
		"text", "varchar",
	}) {
		return "string"
	}
	if in(s, []string{"integer", "smallint", "serial", "tinyint", "tinyint unsigned", "int unsigned", "int"}) {
		return "int"
	}
	if in(s, []string{"numeric", "decimal", "real"}) {
		return "decimal.Decimal"
	}
	if in(s, []string{"bytea"}) {
		return "[]byte"
	}
	if strings.Contains(s, "datetime") || in(s, []string{"time", "timestamp"}) {
		return "time.Time"
	}
	if in(s, []string{"date"}) {
		return "Date"
	}
	if in(s, []string{"bigint", "bigint unsigned", ""}) {
		return "int64"
	}
	return "interface{}"
}

// 包含
func in(s string, arr []string) bool {
	for _, v := range arr {
		if v == s {
			return true
		}
	}
	return false
}
